﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Globalization;

namespace ArgusPCLib
{
	/// <summary>
	/// Defines Extension-Methods for classes in the <see cref="System"/> namespace.
	/// </summary>
	public static class SystemExtensions
	{
		#region Enum
		#endregion

		#region Array
		public static T Last<T>(this T[] array)
		{
			return array[array.Length - 1];
		}

		public static void SwapOrder<T>(this T[] array, int count)
		{
			T[] buffer = new T[array.Length];
			array.CopyTo(buffer, 0);
			for (long i = 0; i < count; i++)
			{
				array[count - (i + 1)] = buffer[i];
			}
		}

		public static void SwapOrder<T>(this T[] array)
		{
			array.SwapOrder(array.Length);
		}

		public static T[] Subarray<T>(this T[] array, int index, int count)
		{
			T[] RetVal = new T[count];
			for (int i = 0; i < count; i++)
				RetVal[i] = array[i + index];
			return RetVal;
		}

		public static T[] Subarray<T>(this T[] array, int index)
		{
			return array.Subarray(index, array.Length - index);
		}

		public static int IndexOf<T>(this T[] array, T item)
		{
			for (int i = 0; i < array.Length; i++)
			{
				if (item.Equals(array[i]))
					return i;
			}
			return -1;
		}
		#endregion

		#region Object
		//public static string ToFormatString(this IFormattable value, IFormatProvider formatProvider, string format)
		//{
		//	return String.Format(formatProvider, "{0:" + format + "}", value);
		//}

		public static string ToStringInvariant(this object obj)
		{
			if (obj == null)
				return "Null";
			if (obj is IFormattable)
			{
				try { return ((IFormattable)obj).ToString(null, CultureInfo.InvariantCulture); }
				catch { }
			}
			//if (obj is IConvertible)
			//{
			//	try { return ((IConvertible)obj).ToString(CultureInfo.InvariantCulture); }
			//	catch { }
			//}
			return obj.ToString();
		}

		public static bool EqualsAny(this object thisObj, params object[] objs)
		{
			foreach (object obj in objs)
			{
				if (thisObj.Equals(obj))
					return true;
			}
			return false;
		}

		public static bool EqualsNone(this object thisObj, params object[] objs)
		{
			foreach (object obj in objs)
			{
				if (thisObj.Equals(obj))
					return false;
			}
			return true;
		}

		public static bool EqualsAny<T>(this T thisObj, params T[] objs)
		{
			foreach (T obj in objs)
			{
				if (thisObj.Equals(obj))
					return true;
			}
			return false;
		}

		public static bool EqualsNone<T>(this T thisObj, params T[] objs)
		{
			foreach (T obj in objs)
			{
				if (thisObj.Equals(obj))
					return false;
			}
			return true;
		}
		#endregion

		#region String
		public static string[] Group(this string s, string start, string end)
		{
			List<string> RetVal = new List<string>();
			int a = s.IndexOf(start)+1;
			int b = s.IndexOf(end, a);
			while (a > 0 && b > -1)
			{
				if (b - a < 1)
					continue;

				RetVal.Add(s.Substring(a, b - a));

				a = s.IndexOf(start, b+1)+1;
				b = s.IndexOf(end, a);
			}
			return RetVal.ToArray();
		}

		public static string[] Group(this string s, char start, char end)
		{
			List<string> RetVal = new List<string>();
			int a = s.IndexOf(start) + 1;
			int b = s.IndexOf(end, a);
			while (a > -1 && b > -1)
			{
				if (b - a < 1)
					continue;

				RetVal.Add(s.Substring(a, b - a));

				a = s.IndexOf(start, b + 1) + 1;
				b = s.IndexOf(end, a);
			}
			return RetVal.ToArray();
		}

		public static string[] Group(this string s, char[] start, char[] end)
		{
			List<string> RetVal = new List<string>();
			int a = s.IndexOfAny(start) + 1;
			int b = s.IndexOfAny(end, a);
			while (a > -1 && b > -1)
			{
				if (b - a < 1)
					continue;

				RetVal.Add(s.Substring(a, b - a));

				a = s.IndexOfAny(start, b + 1) + 1;
				b = s.IndexOfAny(end, a);
			}
			return RetVal.ToArray();
		}

		public static bool Fits(this string s, string pattern)
		{
			return s.Fits(pattern, false);
		}

		/// <summary>
		/// Tests if a <see cref="string"/> fits a given pattern. The pattern can be any string and can contain '*' and '?' as placeholders.
		/// </summary>
		/// <param name="s"></param>
		/// <param name="pattern"></param>
		/// <returns></returns>
		public static bool Fits(this string s, string pattern, bool ignoreCase)
		{
			if (pattern == null)
				throw new ArgumentNullException("pattern");

			if (ignoreCase == true)
			{
				s = s.ToLowerInvariant();
				pattern = pattern.ToLowerInvariant();
			}

			string[] splitAsteriks = pattern.Split('*');

			int index = 0;
			{
				string[] splitQuestion = splitAsteriks[0].Split('?');
				if (s.StartsWith(splitQuestion[0]) == false)
					return false;
				if (s.Length < splitAsteriks[0].Length)
					return false;

				index = splitQuestion[0].Length;
				for (int i = 1; i < splitQuestion.Length; i++)
				{
					index++;
					if (splitQuestion[i].Length < 1)
						continue;
					if (s.Substring(index, splitQuestion[i].Length) != splitQuestion[i])
						return false;
					index += splitQuestion[i].Length;
				}
			}

			for (int i = 1; i < splitAsteriks.Length - 1; i++)
			{
				string[] splitQuestion = splitAsteriks[i].Split('?');
				index = s.IndexOf(splitQuestion[0], index);
				if (index < 0)
					return false;
				if (s.Length - index < splitAsteriks[i].Length)
					return false;
				index += splitQuestion[0].Length;
				for (int j = 1; j < splitQuestion.Length; j++)
				{
					index++;
					if (splitQuestion[j].Length < 1)
						continue;
					if (s.Substring(index, splitQuestion[j].Length) != splitQuestion[j])
						return false;
					index += splitQuestion[j].Length;
				}
			}

			{
				string[] splitQuestion = splitAsteriks[splitAsteriks.Length-1].Split('?');
				if (s.EndsWith(splitQuestion[splitQuestion.Length-1]) == false)
					return false;
				if (s.Length - index < splitAsteriks[splitAsteriks.Length - 1].Length)
					return false;

				index = s.Length - splitAsteriks[splitAsteriks.Length-1].Length;
				for (int i = 0; i < splitQuestion.Length-1; i++)
				{
					index++;
					if (splitQuestion[i].Length < 1)
						continue;
					if (s.Substring(index, splitQuestion[i].Length) != splitQuestion[i])
						return false;
					index += splitQuestion[i].Length;
				}
			}

			return true;
		}

		public static bool FitsAny(this string s, IEnumerable<string> patterns)
		{
			return s.FitsAny(patterns, false);
		}

		public static bool FitsAny(this string s, IEnumerable<string> patterns, bool ignoreCase)
		{
			foreach (string pattern in patterns)
			{
				if (s.Fits(pattern, ignoreCase) == true)
					return true;
			}
			return false;
		}

		public static string RemoveChars(this string s, IEnumerable<char> chars)
		{
			StringBuilder sb = new StringBuilder(s.Length);
			for (int i = 0; i < s.Length; i++)
			{
				if (chars.Contains(s[i]) == false)
					sb.Append(s[i]);
			}
			return sb.ToString();
		}

		public static bool ContainsAny(this string s, IEnumerable<char> chars)
		{
			foreach (char c in chars)
			{
				if (s.IndexOf(c) > -1)
					return true;
			}
			return false;
		}
		#endregion

		#region Type
		public static bool Is(this Type thisType, Type type, bool allowAbstract, bool allowInterface)
		{
			TypeInfo ti = thisType.GetTypeInfo();
			if (!allowAbstract && ti.IsAbstract)
				return false;
			if (!allowInterface && ti.IsInterface)
				return false;
			return type.GetTypeInfo().IsAssignableFrom(ti);
			
			//if (thisType == type || thisType.IsSubclassOf(type) == true)
			//	return true;

			//if (type.IsInterface)
			//{
			//	if (thisType.GetInterfaces().Contains(type))
			//		return true;
			//}

			//return false;
		}

		public static bool Is(this Type thisType, Type type)
		{
			return type.GetTypeInfo().IsAssignableFrom(thisType.GetTypeInfo());
		}
		#endregion

		#region IEnumerable<T>
		//public static bool Contains<T>(this IEnumerable<T> e, T item)
		//{
		//	foreach (T t in e)
		//	{
		//		if (t.Equals(item) == true)
		//			return true;
		//	}

		//	return false;
		//}
		#endregion

		#region Decimal
		/// <summary>
		/// Gets the number of digits after the decimal point.
		/// </summary>
		public static int GetLengthOfDecimalFraction(this decimal d)
		{
			int bits = decimal.GetBits(d)[3];
			bits = bits >> 16;
			bits = bits & byte.MaxValue;
			return bits;
		}

		/// <summary>
		/// Shifts the decimal point to the right.
		/// </summary>
		public static decimal ShiftLeft(this decimal d, int count)
		{
			if (count == 0)
				return d;
			if (count < 0)
				throw new ArgumentOutOfRangeException("Must be non-negative.", "count");
			int[] bits = decimal.GetBits(d);
			int p = (bits[3] >> 16) & byte.MaxValue;
			p -= count;
			if (p < 0)
				throw new OverflowException();
			bits[3] = (bits[3] & (1 << 31)) | (p << 16);
			return new decimal(bits);
		}

		/// <summary>
		/// Shifts the decimal point to the left.
		/// </summary>
		public static decimal ShiftRight(this decimal d, int count)
		{
			if (count == 0)
				return d;
			if (count < 0)
				throw new ArgumentOutOfRangeException("Must be non-negative.", "count");
			int[] bits = decimal.GetBits(d);
			int p = (bits[3] >> 16) & byte.MaxValue;
			p += count;
			if (p < 0 || p > 28)
				throw new OverflowException();
			bits[3] = (bits[3] & (1 << 31)) | (p << 16);
			return new decimal(bits);
		}
		#endregion

		#region DateTime
		public static string ToDateTimeStringInvariant(this DateTime dateTime)
		{
			return dateTime.ToString("yy/MM/dd hh:mm:ss.FFFFFFF");
			return string.Format(CultureInfo.InvariantCulture, "{0:d2}.{1:d2}.{2:d4} {3:d2}:{4:d2}:{5:d2}.{6}",
					dateTime.Day, dateTime.Month, dateTime.Year, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
		}
		#endregion
	}
}
